#include <iostream>

#include "MainWindow.h"
#include "DocumentWindow.h"
#include "ShellMdiArea.h"

#include "simodo/shell/Version.h"

#include <QtWidgets>
#include <QLayout>
#include <QtPrintSupport/QtPrintSupport>

MainWindow::MainWindow()
    : _mdi_area(new ShellMdiArea(this))
    , _doc_collector(this)
    , _shell_access(this)
    , _lsp_management(this)
    , _run_management(*this)
{
    setWindowIcon(QIcon(":/images/simodo-loom.ico"));
    setDockNestingEnabled(true);
    setDockOptions(QMainWindow::AnimatedDocks
                 | QMainWindow::AllowNestedDocks
                 | QMainWindow::AllowTabbedDocks
                //  | QMainWindow::ForceTabbedDocks
                 | QMainWindow::VerticalTabs
                 | QMainWindow::GroupedDragging
                );
    setAnimated(false);
    setDocumentMode(true);
    setCentralWidget(_mdi_area);
    connect(_mdi_area, &QMdiArea::subWindowActivated, this, &MainWindow::updateMenus_slot);
    connect(_mdi_area, &QMdiArea::subWindowActivated, this, &MainWindow::changedCurrentSubWindow);

    _global_messages_dock = new QDockWidget(tr("Global messages"), this);
    /// @todo Изменение размера шрифта нужно сделать централизовано для всех док-панелей
    /// @todo Кроме изменения шрифта нужно изменить (уменьшить) расстояние между элементами,
    /// а то смотрится ущербно
    // // Уменьшаем размер шрифта относительно масштабирования экрана
    // QFont font = _global_messages_dock->font();
    // font.setPointSize(qMax(8,font.pointSize()*8/10));
    // _global_messages_dock->setFont(font);
    _global_messages_dock->setObjectName("Global messages");
    _global_messages_dock->setAllowedAreas(Qt::AllDockWidgetAreas);
    _global_messages_dock->setVisible(false);
    addDockWidget(Qt::BottomDockWidgetArea, _global_messages_dock);

    _global_messages = new GlobalMessages(*_global_messages_dock);
    _global_messages_dock->setWidget(_global_messages);

    sendGlobal(tr("Shell version: %1.%2").arg(simodo::shell::Version_Major).arg(simodo::shell::Version_Minor));

    loadPlugins<shell::Document_plugin>(_document_factories, "*.document-plugin");
    loadPlugins<shell::Panel_plugin>(_panel_factories, "*.panel-plugin");
    loadPlugins<shell::Runner_plugin>(_runner_factories, "*.runner-plugin");

    for(shell::Runner_plugin * f : _runner_factories)
        if (f)
            _runners.push_back(f->createRunner(_shell_access));

    attachPanels();

    createActions();
    createToolbar();
    createStatusBar();

    readSettings();

    updateMenus_slot();

    setWindowTitle("SIMODO - " + QDir::currentPath());
    setUnifiedTitleAndToolBarOnMac(true);

    _lsp_management.prepare();

    sendGlobal(tr("Ready"));
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    _mdi_area->closeAllSubWindows();
    if (_mdi_area->currentSubWindow()) {
        event->ignore();
    } else {
        writeSettings();
        event->accept();
    }
}

void MainWindow::sendGlobal(const QString & text, shell::MessageSeverity severity)
{
    Q_ASSERT(_global_messages);

    _global_messages->sendGlobal(text, severity);

    if (severity > shell::MessageSeverity::Info) {
        if (!_global_messages->isVisible())
        {
            _global_messages_dock->setVisible(true);
            _global_messages_dock->raise();
        }
    }
}

void MainWindow::resetPanels()
{
    for(PanelPluginData & ppd : _panels)
        for(PanelData & pd : ppd.panels)
            if (pd.adaptor) {
                pd.adaptor->reset();
                pd.dock->setWindowTitle(combinePanelTitle(ppd.factory,pd.adaptor->title()));
            }
}

DocumentWindow * MainWindow::findDocumentWindow(const QString & file_name) const
{
    return _mdi_area->findDocumentWindow(file_name);
}

void MainWindow::displayModelingState(const QString & text)
{
    _modeling_state->setText(text);
}

void MainWindow::displayModelingTime(const QString & text)
{
    _modeling_time->setText(text);
}

void MainWindow::displayDocumentInfo(const QString & info)
{
    _document_info->setText(info);
}

void MainWindow::displayCursorPosition(const QString & info)
{
    _cursor_position_info->setText(info);
}

void MainWindow::displayStatistics(const QString & info)
{
    _statistics_info->setText(info);
}

void MainWindow::changedCurrentSubWindow(QMdiSubWindow * subwin)
{
    static QMdiSubWindow * last_subwin = nullptr;

    if (last_subwin == subwin)
        return;

    last_subwin = subwin;

    DocumentWindow * doc = nullptr;

    if (subwin)
        doc = qobject_cast<DocumentWindow *>(subwin->widget());

    QString file_name;

    if (doc)
        file_name = doc->currentFile();

    _run_management.updateRunnerCombo(file_name);

    for(PanelPluginData & ppd : _panels) {
        if (ppd.factory && !ppd.factory->workWithDocuments().empty()) {
            bool activate = doc
                    && ppd.factory->workWithDocuments().contains(doc->view_adaptor()->plugin()->id());
            for(PanelData & pd : ppd.panels) {
                if (pd.dock->widget())
                    pd.dock->widget()->setEnabled(activate);
                if (activate) {
                    pd.dock->setVisible(activate);
                    pd.dock->raise();
                }
            }
        }

        for(PanelData & pd : ppd.panels)
            if (pd.adaptor)
                pd.adaptor->changedCurrentDocument(file_name);
    }

    if (doc == nullptr) {
        displayDocumentInfo("");
        return;
    }

    const QVector<TabParameters> & tab_params = doc->tab_parameters();
    QString info;
    for(int i=0; i < tab_params.size(); ++i) {
        if (i > 0) info += ", ";
        info += tab_params[i].view->plugin()->id();
    }
    displayDocumentInfo(info);
}

static inline QString recentDirsKey() { return QStringLiteral("recentDirList"); }
static inline QString dirKey() { return QStringLiteral("dir"); }

static QStringList readRecentDirs(QSettings & settings)
{
    QStringList result;
    const int   count = settings.beginReadArray(recentDirsKey());

    for (int i = 0; i < count; ++i)
    {
        settings.setArrayIndex(i);
        result.append(settings.value(dirKey()).toString());
    }
    settings.endArray();

    return result;
}

static void writeRecentDirs(const QStringList &dirs, QSettings &settings)
{
    const int count = dirs.size();

    settings.beginWriteArray(recentDirsKey());

    for (int i = 0; i < count; ++i)
    {
        settings.setArrayIndex(i);
        settings.setValue(dirKey(), dirs.at(i));
    }
    settings.endArray();
}

bool MainWindow::hasRecentDirs()
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
    const int count = settings.beginReadArray(recentDirsKey());

    settings.endArray();

    return count > 0;
}

void MainWindow::prependToRecentDirs(const QString &dirName)
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());

    const QStringList oldRecentDirs = readRecentDirs(settings);
    QStringList       recentDirs    = oldRecentDirs;

    recentDirs.removeAll(dirName);

    if (QDir(dirName).exists())
        recentDirs.push_front(dirName);

    if (oldRecentDirs != recentDirs)
        writeRecentDirs(recentDirs, settings);

    setRecentDirsVisible(!recentDirs.isEmpty());
}

void MainWindow::setRecentDirsVisible(bool visible)
{
    _recent_dir_submenu_act->setVisible(visible);
}

void MainWindow::updateRecentDirActions()
{
    QSettings         settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
    const QStringList recentDirs = readRecentDirs(settings);
    const int         count      = qMin(int(MaxRecentDirs), recentDirs.size());
    int               i          = 0;

    for ( ; i < count; ++i) {
        const QString dirName = QFileInfo(recentDirs.at(i)).absoluteFilePath();
        _recent_dir_acts[i]->setText(tr("&%1 %2").arg(i + 1).arg(dirName));
        _recent_dir_acts[i]->setData(recentDirs.at(i));
        _recent_dir_acts[i]->setVisible(true);
    }

    for ( ; i < MaxRecentDirs; ++i)
        _recent_dir_acts[i]->setVisible(false);
}

void MainWindow::openRecentDir()
{
    if (const QAction *action = qobject_cast<const QAction *>(sender()))
        setHomeDirectory(action->data().toString());
}

void MainWindow::requestHomeDirectory()
{
    setHomeDirectory(QFileDialog::getExistingDirectory(this,tr("Home directory"),QDir::currentPath()));
}

void MainWindow::setHomeDirectory(const QString & dir)
{
    // sendGlobal(tr("Home fo '%1' requested").arg(dir), shell::MessageSeverity::Debug);

    if (dir != QDir::currentPath() && !dir.isEmpty()) {
        QDir::setCurrent(dir);
        prependToRecentDirs(dir);
        setWindowTitle("SIMODO - " + QDir::currentPath());
        resetPanels();
    }
}

static inline QString recentFilesKey() { return QStringLiteral("recentFileList"); }
static inline QString fileKey() { return QStringLiteral("file"); }

static QStringList readRecentFiles(QSettings &settings)
{
    QStringList result;
    const int count = settings.beginReadArray(recentFilesKey());
    for (int i = 0; i < count; ++i) {
        settings.setArrayIndex(i);
        result.append(settings.value(fileKey()).toString());
    }
    settings.endArray();
    return result;
}

static void writeRecentFiles(const QStringList &files, QSettings &settings)
{
    const int count = files.size();
    settings.beginWriteArray(recentFilesKey());
    for (int i = 0; i < count; ++i) {
        settings.setArrayIndex(i);
        settings.setValue(fileKey(), files.at(i));
    }
    settings.endArray();
}

bool MainWindow::hasRecentFiles()
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
    const int count = settings.beginReadArray(recentFilesKey());
    settings.endArray();
    return count > 0;
}

void MainWindow::prependToRecentFiles(const QString &file_name)
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());

    const QStringList oldRecentFiles = readRecentFiles(settings);
    QStringList recentFiles = oldRecentFiles;
    recentFiles.removeAll(file_name);
    recentFiles.prepend(file_name);
    if (oldRecentFiles != recentFiles)
        writeRecentFiles(recentFiles, settings);

    setRecentFilesVisible(!recentFiles.isEmpty());
}

void MainWindow::setRecentFilesVisible(bool visible)
{
    _recent_file_submenu_act->setVisible(visible);
}

void MainWindow::updateRecentFileActions()
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());

    const QStringList recentFiles = readRecentFiles(settings);
    const int count = qMin(int(MaxRecentFiles), recentFiles.size());
    int i = 0;
    for ( ; i < count; ++i) {
        const QString file_name = QFileInfo(recentFiles.at(i)).fileName();
        _recent_file_acts[i]->setText(tr("&%1 %2").arg(i + 1).arg(file_name));
        _recent_file_acts[i]->setData(recentFiles.at(i));
        _recent_file_acts[i]->setVisible(true);
    }
    for ( ; i < MaxRecentFiles; ++i)
        _recent_file_acts[i]->setVisible(false);
}

void MainWindow::openRecentFile()
{
    if (const QAction *action = qobject_cast<const QAction *>(sender()))
        openFile(action->data().toString());
}

QString MainWindow::makeFileFilter(shell::service_t service)
{
    QString filter;
    for(const shell::Document_plugin * factory : _document_factories)
        if (shell::check(factory->supports(),service)) {
            QString extensions;
            for(const QString & e : factory->file_extensions()) {
                if (!extensions.isEmpty())
                    extensions += " ";
                extensions += "*." + e;
            }

            if (!filter.isEmpty())
                filter += ";;";
            filter += factory->view_name() + "(" + extensions + ")";
        }

    return filter;
}

void MainWindow::newFile()
{
    QFileDialog dialog(this, tr("New File"), "", makeFileFilter(shell::PS_Doc_New));
    dialog.setFileMode(QFileDialog::AnyFile);
    dialog.setViewMode(QFileDialog::Detail);
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    dialog.setLabelText(QFileDialog::Accept, tr("Create"));

    QStringList fileNames;
    if (dialog.exec())
        fileNames = dialog.selectedFiles();

    if (!fileNames.isEmpty())
        openFile(fileNames.front(), true);
}

void MainWindow::open()
{
    const QString file_name = QFileDialog::getOpenFileName(this,
                                            tr("Open File"),
                                            "",
                                            makeFileFilter(shell::PS_Doc_Open));
    if (!file_name.isEmpty()) {
        openFile(file_name);
    }
}

bool MainWindow::openFile(const QString &file_name, bool create)
{
    if (file_name == "Setup") {
        const QList<QMdiSubWindow *> subWindows = _mdi_area->subWindowList();
        for (QMdiSubWindow * subwin : subWindows) {
            DocumentWindow * document_window = qobject_cast<DocumentWindow *>(subwin->widget());
            if (document_window && document_window->currentFile() == "Setup") {
                _mdi_area->setActiveSubWindow(subwin);
                statusBar()->showMessage(tr("Setup opened"), 2000);
                return true;
            }
        }
        /// \todo Setup: Добавить создание Setup!
        return false;
    }

    QMdiSubWindow * existing = findMdiChild(file_name); 
    if (existing) {
        _mdi_area->setActiveSubWindow(existing);
        return true;
    }
    const bool succeeded = loadFile(file_name, create);
    if (succeeded)
        statusBar()->showMessage(tr("File loaded"), 2000);
    return succeeded;
}

bool MainWindow::openReports(const QString & path, const std::vector<simodo::lsp::SimodoCommandReport> & reports)
{
    if (reports.empty())
        return false;

    QStringList content = QString::fromStdU16String(reports[0].text).split('\n');
    QStringList text;
    
    for(int i = 0; i < content.size(); ++i) 
        if (content.at(i).startsWith('#'))
            executeVisualizationCommand(content.at(i));
        else
            text.append(content.at(i));

    for(PanelPluginData & ppd : _panels)
        if (ppd.factory && ppd.plugin_id == "ReportText") {
            PanelData * panel_data = nullptr;

            for(PanelData & pd : ppd.panels) 
                if (pd.adaptor->title() == QFileInfo(path).fileName()) {
                    panel_data = & pd;
                    break;
                }

            if (!panel_data)
                for(PanelData & pd : ppd.panels) 
                    if (pd.adaptor->title().isEmpty()) {
                        panel_data = & pd;
                        break;
                    }

            if (!panel_data) {
                if (shell::check(ppd.factory->supports(), shell::PS_Pan_Singleton))
                    panel_data = & ppd.panels.back();
                else if (attachNewPanelToPanelPluginData(ppd,true))
                    panel_data = & ppd.panels.back();
            }

            if (panel_data) {
                QJsonObject data;
                data.insert("file", QFileInfo(path).fileName());
                data.insert("text", text.join('\n'));
                panel_data->adaptor->acceptData(data);

                panel_data->dock->setWindowTitle(combinePanelTitle(ppd.factory,QFileInfo(path).fileName()));
                panel_data->dock->setVisible(true);
                panel_data->dock->raise();
                return true;
            }
            break;
        }

    return false;
}

bool MainWindow::loadFile(const QString &file_name, bool create)
{
    std::shared_ptr<shell::DocumentAdaptor_interface>  
                                doc_adaptor = _doc_collector.find(file_name);
    DocumentWindow *            doc_window  = createDocumentWindow(file_name, doc_adaptor);
    if (doc_window == nullptr)
        return false;

    bool succeeded = true;
    if (doc_adaptor)
        sendGlobal(QString(tr("Reusing: %1")).arg(file_name));
    else {
        if (!create) {
            sendGlobal(QString(tr("Loading: %1")).arg(file_name));
            succeeded = doc_window->loadFile(file_name);
        }
        if (succeeded)
            _doc_collector.add(file_name, doc_window->document_adaptor());
    }

    if (succeeded) {
        lsp().opened(file_name, doc_adaptor ? doc_adaptor : doc_window->document_adaptor());
        QMdiSubWindow * subwin = _mdi_area->addSubWindow(doc_window);
        QIcon icon = _fs_model.fileIcon(_fs_model.index(doc_window->currentFile()));
        subwin->setWindowIcon(icon);
        subwin->show();
        doc_window->view_adaptor()->readyToWork(doc_window->view_adaptor()->document_widget());
        doc_window->document_widget()->setFocus();
        MainWindow::prependToRecentFiles(file_name);

        QString new_path = doc_window->currentFile();

        for(const QString & loaded_path : _mdi_area->history())
            if (loaded_path != new_path && QFileInfo(loaded_path).fileName() == QFileInfo(new_path).fileName()) {
                DocumentWindow * dw = findDocumentWindow(loaded_path);
                if (dw)
                    dw->setCurrentFile(loaded_path);
            }
    }

    return succeeded;
}

void MainWindow::save()
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc && doc->save())
        statusBar()->showMessage(tr("File saved"), 2000);
}

void MainWindow::saveAs()
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc && doc->saveAs()) {
            statusBar()->showMessage(tr("File saved"), 2000);
            MainWindow::prependToRecentFiles(doc->currentFile());
    }
}

void MainWindow::saveAll()
{
    bool has_saved = false;

    foreach (QMdiSubWindow *subwin, _mdi_area->subWindowList()) {
        DocumentWindow * doc = qobject_cast<DocumentWindow *>(subwin->widget());

        if (doc->save())
            has_saved = true;
    }

    if (has_saved)
        statusBar()->showMessage(tr("All documents saved"), 2000);
}

void MainWindow::print()
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog)
    DocumentWindow * doc = activeDocumentWindow();
    if (doc) {
        QPrinter printer(QPrinter::HighResolution);
        QPrintDialog *dlg = new QPrintDialog(&printer, this);
        if (doc->hasSelection())
            dlg->addEnabledOption(QAbstractPrintDialog::PrintSelection);
        dlg->setWindowTitle(tr("Print Document"));
        if (dlg->exec() == QDialog::Accepted)
            doc->print(&printer);
        delete dlg;
    }
#endif
}

void MainWindow::preview()
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printpreviewdialog)
    DocumentWindow * wid = activeDocumentWindow();
    if (wid) {
        QPrinter printer(QPrinter::HighResolution);
        QPrintPreviewDialog preview(&printer, this);
        connect(&preview, &QPrintPreviewDialog::paintRequested, this, &MainWindow::printPreview);
        preview.exec();
    }
#endif
}

void MainWindow::printPreview(QPrinter * printer)
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
    DocumentWindow * doc = activeDocumentWindow();
    if (doc)
        doc->print(printer);
#else
    Q_UNUSED(printer)
#endif
}

void MainWindow::printPdf()
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printer)
    DocumentWindow * doc = activeDocumentWindow();
    if (doc) {
        QFileDialog fileDialog(this, tr("Export PDF"));
        fileDialog.setAcceptMode(QFileDialog::AcceptSave);
        fileDialog.setMimeTypeFilters(QStringList("application/pdf"));
        fileDialog.setDefaultSuffix("pdf");
        if (fileDialog.exec() != QDialog::Accepted)
            return;
        QString file_name = fileDialog.selectedFiles().first();
        QPrinter printer(QPrinter::HighResolution);
        printer.setOutputFormat(QPrinter::PdfFormat);
        printer.setOutputFileName(file_name);
        doc->print(&printer);
        statusBar()->showMessage(tr("Exported \"%1\"")
                                .arg(QDir::toNativeSeparators(file_name)));
    }
#endif
}

#ifndef QT_NO_CLIPBOARD
void MainWindow::cut()
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc) {
        doc->cutFromDocument();
    }
}

void MainWindow::copy()
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc)
        doc->copyFromDocument();
}

void MainWindow::paste()
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc)
        doc->pasteToDocument();
}
#endif

void MainWindow::statusBarViewing()
{
    bool statusbar_viewing = _statusbar_action->isChecked();
    statusBar()->setVisible(statusbar_viewing);
}

void MainWindow::help()
{
    QMessageBox::about(this, tr("SIMODO shell Help"),
            tr("Probably here you need to open a page on our future site, "
               "where help will be given."));
}

void MainWindow::about()
{
    QMessageBox::about(this, tr("SIMODO shell About"),
            tr("<p>SIMODO shell is an integrated development environment for the SIMODO project.</p>") +
            tr("<p><b>The main objective of the SIMODO project</b> is to provide mathematical modeling software tools for "
               "performing research on large complex models covering several domain.</p>") +
            tr("<p>The mirror with the project's source codes: <a href=\"https://gitlab.com/mfetisov_lsx/simodo/shell\">"
               "https://gitlab.com/mfetisov_lsx</a>.</p>") + 
            tr("<p>The site of the project: <a href=\"http://simodo.ru/\">http://simodo.ru</a>.</p>") + 
            tr("<p>Version: %1.%2 </p>").arg(simodo::shell::Version_Major).arg(simodo::shell::Version_Minor)
            );
}

void MainWindow::attachPanels()
{
    for(shell::Panel_plugin * factory : _panel_factories) {
        _panels.push_back({factory, factory->id(), factory->designation(), {}});

        if (shell::check(factory->supports(), shell::PS_Pan_Singleton))
            attachNewPanelToPanelPluginData(_panels.back());
        else
            for(int i=0; i < INITIAL_QUANTITY_OF_DYNAMIC_PANELS_BY_PLUGIN; ++i)
                attachNewPanelToPanelPluginData(_panels.back());
    }
}

bool MainWindow::attachNewPanelToPanelPluginData(const PanelPluginData & panel_plugin_data, bool restore_state)
{
    PanelPluginData & ppd = const_cast<PanelPluginData &>(panel_plugin_data);

    shell::PanelAdaptor_interface * panel_adaptor = ppd.factory->createPanelAdaptor(_shell_access);

    if (!panel_adaptor)
        return false;

    QDockWidget * dock = new QDockWidget(combinePanelTitle(ppd.factory,panel_adaptor->title()), this);

    dock->setObjectName(ppd.factory->id() + "_" + QString::fromStdString(std::to_string(ppd.panels.size())));
    dock->setAllowedAreas(ppd.factory->allowed_areas());
    addDockWidget(ppd.factory->attach_to(), dock);

    dock->setWidget(panel_adaptor->panel_widget());
    dock->setVisible(shell::check(ppd.factory->supports(), shell::PS_Pan_ShowOnStart));
    if (dock->isVisible())
        dock->show();

    ppd.panels.push_back({panel_adaptor, dock});

    if (restore_state) {
        bool ok = restoreDockWidget(dock);
        qDebug() << "Restore " << dock->objectName() << ": " << ok;
    }

    return true;
}

void MainWindow::executeVisualizationCommand(const QString & command)
{
    const QString time_marker = "#Time:";
    const QString value_marker = "#Values:";

    if (command.startsWith(time_marker))
    {
        displayModelingTime(command.mid(time_marker.length()));
        return;
    }

    if (!command.startsWith(value_marker)) return;

    QString panel_type = command.mid(value_marker.length());
    int     dot_1      = command.indexOf('.');

    panel_type.truncate(dot_1-value_marker.length());

    int     dot_2       = command.indexOf('.', dot_1+1);
    int     dot_3       = command.indexOf('.', dot_2+1);
    int     semicolon_2 = command.indexOf(':', dot_3+1);
    QString func        = command.mid(dot_3+1, semicolon_2-dot_3-1);

    QVector<QString> data;
    data.reserve(4);

    for(int start_pos = semicolon_2 + 1; 
        start_pos > 0; 
        start_pos = command.indexOf('/', start_pos) + 1) 
    {
        int end_pos   = command.indexOf('/', start_pos);
        if (end_pos < 0)
            end_pos = command.length();

        QString element = command.mid(start_pos, end_pos-start_pos);
        data.push_back(element);
    }

    QString panel_index_string = command.mid(dot_1+1, dot_2-dot_1-1);
    size_t  panel_index        = panel_index_string.toUInt();

    for (const PanelPluginData & ppd : _panels)
    {
        if (ppd.plugin_designation != panel_type) continue;

        shell::AdaptorModeling_interface * modeling_interface = nullptr;

        if (panel_index < ppd.panels.size()) {
            modeling_interface = ppd.panels[panel_index].adaptor->getModelingInterface();
        }
        else if (panel_index == ppd.panels.size() && (ppd.factory->supports() & shell::PS_Pan_Singleton) == 0) {
            if (attachNewPanelToPanelPluginData(ppd)) {
                Q_ASSERT(panel_index < ppd.panels.size());
                modeling_interface = ppd.panels[panel_index].adaptor->getModelingInterface();
            }
        }

        if (!modeling_interface) continue;

        modeling_interface->acceptData(func, data);
    }
}

void MainWindow::updateMenus_slot()
{
    DocumentWindow * dw          = activeDocumentWindow();
    bool             hasMdiChild = (dw != nullptr);

    _save_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Save));
    _save_as_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Save));
    _print_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Print));
    _print_preview_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Print));
    _print_pdf_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_ExportToPdf));
    _export_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Export));
    _edit_undo_action->setEnabled(hasMdiChild && dw->hasUndo());
    _edit_redo_action->setEnabled(hasMdiChild && dw->hasRedo());
#ifndef QT_NO_CLIPBOARD
    _paste_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Paste));
#endif
    _zoom_in_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Zoom));
    _zoom_out_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Zoom));
    _zoom_original_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Zoom));
    // _find_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::Find));
    // _replace_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::Replace));
    _statusbar_action->setChecked(statusBar()->isEnabled());
    _close_act->setEnabled(hasMdiChild);
    _close_all_act->setEnabled(hasMdiChild);
    _tile_act->setEnabled(hasMdiChild);
    _cascade_act->setEnabled(hasMdiChild);
    _split_horizontally_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Splittable));
    _split_vertically_action->setEnabled(hasMdiChild && shell::check(dw->supports(),shell::PS_Doc_Splittable));
    _next_act->setEnabled(hasMdiChild);
    _previous_act->setEnabled(hasMdiChild);
    _window_menu_separator_act->setVisible(hasMdiChild);

#ifndef QT_NO_CLIPBOARD
    bool hasSelection = (dw && dw->hasSelection());
    _cut_action->setEnabled(hasSelection && !shell::check(dw->supports(),shell::PS_Doc_ReadOnly));
    _copy_action->setEnabled(hasSelection);
#endif
    shell::RunnerState runner_state = _run_management.state();
    bool        runner_ready_to_run = dw && shell::check(dw->supports(),shell::PS_Doc_Run) && _run_management.isRunnable(dw->currentFile());

    _run_action->setEnabled(runner_state == shell::RunnerState::Paused
                    || (runner_state != shell::RunnerState::Running && runner_ready_to_run));
    _pause_action->setEnabled(runner_state == shell::RunnerState::Running); 
    _stop_action->setEnabled(runner_state != shell::RunnerState::Stoped);
}

void MainWindow::updateViewMenu()
{
    _view_menu->clear();

    _view_menu->addAction(_quick_action);
    _view_menu->addAction(_modeling_action);
    _view_menu->addAction(_statusbar_action);
    _view_menu->addAction(_message_dock_action);

    if (!_panels.empty()) {
        _view_menu->addSeparator();
        int i = 3;
        for(PanelPluginData & ppd : _panels)
            for(PanelData & pd : ppd.panels) 
                if (shell::check(ppd.factory->supports(),shell::PS_Pan_Singleton)) {
                    QString panel_title = combinePanelTitle(ppd.factory,pd.adaptor->title());
                    ppd.menu_action = new QAction(panel_title, this);
                    ppd.menu_action->setCheckable(true);
                    ppd.menu_action->setChecked(pd.dock->isVisible());
                    ppd.menu_action->setStatusTip(tr("Show or hide %1").arg(panel_title));
                    if (i <= 10)
                        ppd.menu_action->setShortcut(Qt::ALT + Qt::Key_0 + i%10);

                    connect(ppd.menu_action, &QAction::triggered, pd.dock, &QDockWidget::setVisible);
                    connect(pd.dock, &QDockWidget::visibilityChanged, ppd.menu_action, &QAction::setChecked);
                    i++;
                    _view_menu->addAction(ppd.menu_action);
                }
        _view_menu->addSeparator();
        for(PanelPluginData & ppd : _panels)
            for(size_t i=0; i< ppd.panels.size(); ++i) {
                PanelData & pd = ppd.panels[i];
                if (!shell::check(ppd.factory->supports(),shell::PS_Pan_Singleton)) {
                    QString panel_title = pd.adaptor->title().isEmpty()
                                        ? ppd.factory->id()
                                        : combinePanelTitle(ppd.factory,pd.adaptor->title());
                    panel_title += "\t(#" + QString::fromStdString(std::to_string(i)) + ")";
                    ppd.menu_action = new QAction(panel_title, this);
                    ppd.menu_action->setCheckable(true);
                    ppd.menu_action->setChecked(pd.dock->isVisible());
                    ppd.menu_action->setStatusTip(tr("Show or hide %1").arg(panel_title));

                    connect(ppd.menu_action, &QAction::triggered, pd.dock, &QDockWidget::setVisible);
                    connect(pd.dock, &QDockWidget::visibilityChanged, ppd.menu_action, &QAction::setChecked);
                    _view_menu->addAction(ppd.menu_action);
                }
            }
    }

}

void MainWindow::updateWindowMenu()
{
    DocumentWindow * active_document = activeDocumentWindow();
    bool             hasMdiChild     = (active_document != nullptr);

    _window_menu->clear();
    _window_menu->addAction(_close_act);
    _window_menu->addAction(_close_all_act);
    _window_menu->addSeparator();
    _window_menu->addAction(_tile_act);
    _window_menu->addAction(_cascade_act);
    _window_menu->addSeparator();
    _window_menu->addAction(_split_horizontally_action);
    _split_horizontally_action->setChecked(hasMdiChild && active_document->orientation() == Qt::Horizontal);
    _window_menu->addAction(_split_vertically_action);
    _split_vertically_action->setChecked(hasMdiChild && active_document->orientation() == Qt::Vertical);
    _window_menu->addSeparator();
    _window_menu->addAction(_previous_act);
    _window_menu->addAction(_next_act);
    _window_menu->addAction(_window_menu_separator_act);

    const std::vector<QString> & history = _mdi_area->history();
    _window_menu_separator_act->setVisible(!history.empty());

    for (size_t i = 0; i < history.size(); ++i) {
        QString          path = history[history.size()-i-1];
        DocumentWindow * document_window = findDocumentWindow(path);
        if (document_window) {
            QIcon icon = filesystem_model().fileIcon(filesystem_model().index(document_window->currentFile()));

            if (icon.isNull())
                icon = document_window->view_adaptor()->plugin()->icon();

            if (icon.isNull())
                icon = windowIcon();

            QAction *action = _window_menu->addAction(icon, document_window->userFriendlyCurrentFile(), this, 
                    [this, path] {
                        openFile(path);
                    });
            action->setCheckable(true);
            action->setChecked(active_document == document_window);
            _window_menu->addAction(action);
        }
    }
}

void MainWindow::regroupDocumentHorizontally(bool checked)
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc)
        doc->regroupViews(checked ? Qt::Horizontal : 0);
}

void MainWindow::regroupDocumentVertically(bool checked)
{
    DocumentWindow * doc = activeDocumentWindow();
    if (doc)
        doc->regroupViews(checked ? Qt::Vertical : 0);
}

shell::Document_plugin * MainWindow::findDocumentPlugin(QString extention)
{
    shell::Document_plugin * doc_plugin = nullptr;
    for(shell::Document_plugin * factory : _document_factories) {
        for (const QString & e : factory->file_extensions())
            if (extention.toUpper() == e.toUpper()) {
                doc_plugin = factory;
                break;
            }
        if (doc_plugin)
            break;
    }

    if (!doc_plugin)
        for(shell::Document_plugin * factory : _document_factories)
            if (factory->file_extensions().contains("*")) {
                doc_plugin = factory;
                break;
            }

    return doc_plugin;
}

DocumentWindow * MainWindow::createDocumentWindow(const QString & file_name,
                                                  std::shared_ptr<shell::DocumentAdaptor_interface> doc_adaptor,
                                                  QString extention)
{
    if (extention.isEmpty())
        extention = QFileInfo(file_name).suffix();

    shell::Document_plugin *       doc_plugin   = findDocumentPlugin(extention);
    shell::ViewAdaptor_interface * view_adaptor = nullptr;

    if (doc_plugin) {
        view_adaptor = doc_plugin->createViewAdaptor(_shell_access, doc_adaptor, file_name);

        if (view_adaptor == nullptr) {
            sendGlobal(QString(tr("Unable to create document for extension %1"))
                    .arg(extention),
                    shell::MessageSeverity::Error);
            return nullptr;
        }
    }

    if (!view_adaptor) {
        sendGlobal(QString(tr("File extension %1 not supported")).arg(extention),
                shell::MessageSeverity::Error);
        return nullptr;
    }

    QVector<shell::ViewAdaptor_interface *> views;
    views.append(view_adaptor);

    if (doc_plugin)
        if (!doc_plugin->alternate_view_mnemonics(extention).empty())
            for(shell::Document_plugin * factory : _document_factories)
                if (doc_plugin != factory
                && doc_plugin->alternate_view_mnemonics(extention).contains(factory->id())) {
                    shell::ViewAdaptor_interface * view = factory->createViewAdaptor(_shell_access, nullptr, file_name);
                    if (view) {
                        if (shell::check(doc_plugin->supports(),shell::PS_Doc_ReadOnly))
                            view->setReadOnly();
                        views.append(view);
                    }
                }

    DocumentWindow * doc = new DocumentWindow(this, views);
    doc->setCurrentFile(file_name);
    // _mdi_area->addSubWindow(doc);
    return doc;
}

void MainWindow::createActions()
{
    QMenu * file_menu = menuBar()->addMenu(tr("&File"));

    QMenu * recentDirMenu = file_menu->addMenu(tr("Recent home directory..."));
    connect(recentDirMenu, &QMenu::aboutToShow, this, &MainWindow::updateRecentDirActions);
    _recent_dir_submenu_act = recentDirMenu->menuAction();
    _recent_dir_submenu_act->setStatusTip(tr("Set recent home directory..."));

    for (int i = 0; i < MaxRecentDirs; ++i) {
        _recent_dir_acts[i] = recentDirMenu->addAction(QString(), this, & MainWindow::openRecentDir);
        _recent_dir_acts[i]->setVisible(false);
    }

    setRecentDirsVisible(MainWindow::hasRecentDirs());

    const QIcon dirIcon = QIcon::fromTheme("go-home", QIcon(":/images/user-home"));
    _set_work_dir_action = new QAction(dirIcon, tr("New &home..."), this);
    _set_work_dir_action->setStatusTip(tr("Set up home in other directory"));
    connect(_set_work_dir_action, &QAction::triggered, this, &MainWindow::requestHomeDirectory);
    file_menu->addAction(_set_work_dir_action);

    file_menu->addSeparator();

    const QIcon recentIcon = QIcon::fromTheme("document-open-recent", QIcon(":/images/document-open-recent"));
    QMenu *recentMenu = file_menu->addMenu(recentIcon, tr("Recent file..."));
    connect(recentMenu, &QMenu::aboutToShow, this, &MainWindow::updateRecentFileActions);
    _recent_file_submenu_act = recentMenu->menuAction();
    _recent_file_submenu_act->setStatusTip(tr("Open recent file..."));

    for (int i = 0; i < MaxRecentFiles; ++i) {
        _recent_file_acts[i] = recentMenu->addAction(QString(), this, &MainWindow::openRecentFile);
        _recent_file_acts[i]->setVisible(false);
    }

    setRecentFilesVisible(MainWindow::hasRecentFiles());

    const QIcon newIcon = QIcon::fromTheme("document-new", QIcon(":/images/document-new"));
    _new_action = new QAction(newIcon, tr("&New..."), this);
    _new_action->setShortcuts(QKeySequence::New);
    _new_action->setStatusTip(tr("Create an new file"));
    connect(_new_action, &QAction::triggered, this, &MainWindow::newFile);
    file_menu->addAction(_new_action);

    const QIcon openIcon = QIcon::fromTheme("document-open", QIcon(":/images/document-open"));
    _open_action = new QAction(openIcon, tr("&Open..."), this);
    _open_action->setShortcuts(QKeySequence::Open);
    _open_action->setStatusTip(tr("Open an existing file"));
    connect(_open_action, &QAction::triggered, this, &MainWindow::open);
    file_menu->addAction(_open_action);

    const QIcon saveIcon = QIcon::fromTheme("document-save", QIcon(":/images/document-save"));
    _save_action = new QAction(saveIcon, tr("&Save"), this);
    _save_action->setShortcuts(QKeySequence::Save);
    _save_action->setStatusTip(tr("Save the document to disk"));
    connect(_save_action, &QAction::triggered, this, &MainWindow::save);
    file_menu->addAction(_save_action);

    const QIcon saveAsIcon = QIcon::fromTheme("document-save-as", QIcon(":/images/document-save-as"));
    _save_as_action = new QAction(saveAsIcon, tr("Save &As..."), this);
    _save_as_action->setShortcuts(QKeySequence::SaveAs);
    _save_as_action->setStatusTip(tr("Save the document under a new name"));
    connect(_save_as_action, &QAction::triggered, this, &MainWindow::saveAs);
    file_menu->addAction(_save_as_action);

    const QIcon save_all_icon = QIcon::fromTheme("document-save-all", QIcon(":/images/document-save-all"));
    _save_all_action = new QAction(save_all_icon, tr("Save All"), this);
    _save_all_action->setStatusTip(tr("Save all documents"));
    connect(_save_all_action, &QAction::triggered, this, &MainWindow::saveAll);
    file_menu->addAction(_save_all_action);

    file_menu->addSeparator();

    const QIcon printIcon = QIcon::fromTheme("document-print", QIcon(":/images/document-print"));
    _print_action = file_menu->addAction(printIcon, tr("&Print..."), this, &MainWindow::print);
    _print_action->setPriority(QAction::LowPriority);
    _print_action->setShortcut(QKeySequence::Print);

    const QIcon filePrintIcon = QIcon::fromTheme("document-preview", QIcon(":/images/document-preview"));
    _print_preview_action = file_menu->addAction(filePrintIcon, tr("Preview..."), this, &MainWindow::preview);

    _print_pdf_action = file_menu->addAction(tr("&Export PDF..."), this, &MainWindow::printPdf);
    _print_pdf_action->setPriority(QAction::LowPriority);

    const QIcon exportIcon = QIcon::fromTheme("document-export", QIcon(":/images/document-export"));
    _export_action = file_menu->addAction(exportIcon, tr("&Export..."), this, [this](){
                                                QMessageBox::critical(this, tr("Error"), tr("Not implemented"));
                                            });
    _export_action->setPriority(QAction::LowPriority);

    // const QIcon setupIcon = QIcon::fromTheme("document-save-as", QIcon(":/images/document-save-as"));
    _setup_action = new QAction(tr("&Setup..."), this);
    _setup_action->setShortcut(Qt::CTRL + Qt::Key_Comma);
    _setup_action->setStatusTip(tr("Open setup window"));
    connect(_setup_action, &QAction::triggered, this, 
            [this](){
                const QList<QMdiSubWindow *> subWindows = _mdi_area->subWindowList();
                for (QMdiSubWindow * subwin : subWindows) {
                    DocumentWindow * document_window = qobject_cast<DocumentWindow *>(subwin->widget());
                    if (document_window && document_window->currentFile() == "Setup") {
                        _mdi_area->setActiveSubWindow(subwin);
                        return;
                    }
                }

                shell::Document_plugin * doc_plugin = nullptr;
                for(shell::Document_plugin * p : _document_factories)
                    if (p->id() == "setup") {
                        doc_plugin = p;
                        break;
                    }

                if (!doc_plugin) {
                    sendGlobal(QString(tr("Unable to create document for setup")),
                            shell::MessageSeverity::Error);
                    return;
                }

                shell::ViewAdaptor_interface * view_adaptor =
                    doc_plugin->createViewAdaptor(_shell_access, nullptr, "Setup");

                if (view_adaptor == nullptr) {
                    sendGlobal(QString(tr("Unable to create view for setup")),
                            shell::MessageSeverity::Error);
                    return;
                }

                DocumentWindow * doc = new DocumentWindow(this, {view_adaptor});
                doc->setCurrentFile("Setup");

                QMdiSubWindow * subwin = _mdi_area->addSubWindow(doc);
                subwin->setWindowIcon(doc_plugin->icon());
                subwin->show();

                doc->view_adaptor()->readyToWork(doc->view_adaptor()->document_widget());
                doc->document_widget()->setFocus();
            });
    file_menu->addAction(_setup_action);

    file_menu->addSeparator();

    const QIcon exitIcon = QIcon::fromTheme("application-exit", QIcon(":/images/application-exit"));
    QAction *exitAct = file_menu->addAction(exitIcon, tr("E&xit"), qApp, &QApplication::closeAllWindows);
    exitAct->setShortcuts(QKeySequence::Quit);
    exitAct->setStatusTip(tr("Exit the application"));
    file_menu->addAction(exitAct);

    QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));

    const QIcon undo_icon = QIcon::fromTheme("edit-undo", QIcon(":/images/edit-undo"));
    _edit_undo_action = new QAction(undo_icon, tr("&Undo"), this);
    _edit_undo_action->setShortcuts(QKeySequence::Undo);
    _edit_undo_action->setStatusTip(tr("Undo last text modification"));
    editMenu->addAction(_edit_undo_action);

    const QIcon redo_icon = QIcon::fromTheme("edit-redo", QIcon(":/images/edit-redo"));
    _edit_redo_action = new QAction(redo_icon, tr("Red&o"), this);
    _edit_redo_action->setShortcuts(QKeySequence::Redo);
    _edit_redo_action->setStatusTip(tr("Redo last undo action"));
    editMenu->addAction(_edit_redo_action);

#ifndef QT_NO_CLIPBOARD
    editMenu->addSeparator();

    const QIcon cutIcon = QIcon::fromTheme("edit-cut", QIcon(":/images/cut"));
    _cut_action = new QAction(cutIcon, tr("Cu&t"), this);
    _cut_action->setShortcuts(QKeySequence::Cut);
    _cut_action->setStatusTip(tr("Cut the current selection's contents to the clipboard"));
    connect(_cut_action, &QAction::triggered, this, &MainWindow::cut);
    editMenu->addAction(_cut_action);

    const QIcon copyIcon = QIcon::fromTheme("edit-copy", QIcon(":/images/copy"));
    _copy_action = new QAction(copyIcon, tr("&Copy"), this);
    _copy_action->setShortcuts(QKeySequence::Copy);
    _copy_action->setStatusTip(tr("Copy the current selection's contents to the clipboard"));
    connect(_copy_action, &QAction::triggered, this, &MainWindow::copy);
    editMenu->addAction(_copy_action);

    const QIcon pasteIcon = QIcon::fromTheme("edit-paste", QIcon(":/images/paste"));
    _paste_action = new QAction(pasteIcon, tr("&Paste"), this);
    _paste_action->setShortcuts(QKeySequence::Paste);
    _paste_action->setStatusTip(tr("Paste the clipboard's contents into the current selection"));
    connect(_paste_action, &QAction::triggered, this, &MainWindow::paste);
    editMenu->addAction(_paste_action);
#endif

    editMenu->addSeparator();

    const QIcon zoomInIcon = QIcon::fromTheme("zoom-in", QIcon(":/images/zoom-in"));
    _zoom_in_action = new QAction(zoomInIcon, tr("Zoom in"), this);
    _zoom_in_action->setShortcut(QKeySequence::ZoomIn);
    _zoom_in_action->setStatusTip(tr("Zoom in"));
    connect(_zoom_in_action, &QAction::triggered, this, [this](){
        DocumentWindow * doc = activeDocumentWindow();
        if (doc)
            doc->zoomIn();
    });
    editMenu->addAction(_zoom_in_action);

    const QIcon zoomOutIcon = QIcon::fromTheme("zoom-out", QIcon(":/images/zoom-out"));
    _zoom_out_action = new QAction(zoomOutIcon, tr("Zoom out"), this);
    _zoom_out_action->setShortcut(QKeySequence::ZoomOut);
    _zoom_out_action->setStatusTip(tr("Zoom out"));
    connect(_zoom_out_action, &QAction::triggered, this, [this](){
        DocumentWindow * doc = activeDocumentWindow();
        if (doc)
            doc->zoomOut();
    });
    editMenu->addAction(_zoom_out_action);

    const QIcon zoomOriginalIcon = QIcon::fromTheme("zoom-original", QIcon(":/images/zoom-original"));
    _zoom_original_action = new QAction(zoomOriginalIcon, tr("Zoom original"), this);
    _zoom_original_action->setShortcut(Qt::CTRL + Qt::Key_0);
    _zoom_original_action->setStatusTip(tr("Zoom original"));
    connect(_zoom_original_action, &QAction::triggered, this, [this](){
        DocumentWindow * doc = activeDocumentWindow();
        if (doc)
            doc->zoomZero();
    });
    editMenu->addAction(_zoom_original_action);

    editMenu->addSeparator();

    const QIcon findIcon = QIcon::fromTheme("edit-find", QIcon(":/images/edit-find"));
    _find_action = new QAction(findIcon, tr("&Find"), this);
    _find_action->setShortcut(QKeySequence::Find);
    _find_action->setStatusTip(tr("Show/hide find dialog"));
    connect(_find_action, &QAction::triggered, this, [this](bool ){
        for(PanelPluginData & ppd : _panels)
            if (shell::check(ppd.factory->supports(),shell::PS_Pan_Find)) 
                for(PanelData & pd : ppd.panels) {
                    pd.adaptor->activateService(shell::PS_Pan_Find);
                    pd.dock->setVisible(true);
                    pd.dock->raise();
                }
    });
    editMenu->addAction(_find_action);

    const QIcon replaceIcon = QIcon::fromTheme("edit-find-replace", QIcon(":/images/edit-find-replace"));
    _replace_action = new QAction(replaceIcon, tr("&Replace"), this);
    _replace_action->setShortcut(QKeySequence::Replace);
    _replace_action->setStatusTip(tr("Show/hide replace dialog"));
    connect(_replace_action, &QAction::triggered, this, [this](bool ){
        for(auto & ppd : _panels)
            if (shell::check(ppd.factory->supports(),shell::PS_Pan_Replace)) 
                for(PanelData & pd : ppd.panels) {
                    pd.adaptor->activateService(shell::PS_Pan_Replace);
                    pd.dock->setVisible(true);
                    pd.dock->raise();
                }
    });
    editMenu->addAction(_replace_action);

    // run_menu
    QMenu *run_menu = menuBar()->addMenu(tr("&Run"));

    const QIcon runIcon = QIcon::fromTheme("media-playback-start", QIcon(":/images/media-playback-start"));
    _run_action = new QAction(runIcon, tr("&Run"), this);
    _run_action->setShortcut(Qt::Key_F5);
    _run_action->setStatusTip(tr("Execute current edit text"));
    connect(_run_action, &QAction::triggered, this, [this]() { 
                DocumentWindow * doc_win = activeDocumentWindow();
                if (doc_win)
                    _run_management.startModeling(doc_win->currentFile()); 
            });
    run_menu->addAction(_run_action);

    const QIcon pauseIcon = QIcon::fromTheme("media-playback-pause", QIcon(":/images/media-playback-pause"));
    _pause_action = new QAction(pauseIcon, tr("&Pause"), this);
    _pause_action->setShortcut(Qt::Key_Pause);
    _pause_action->setStatusTip(tr("Pause current execution"));
    connect(_pause_action, &QAction::triggered, &_run_management, &RunManagement::pauseModeling);
    run_menu->addAction(_pause_action);

    const QIcon stopIcon = QIcon::fromTheme("media-playback-stop", QIcon(":/images/media-playback-stop"));
    _stop_action = new QAction(stopIcon, tr("&Stop"), this);
    _stop_action->setShortcut(Qt::Key_Escape);
    _stop_action->setStatusTip(tr("Terminate current execution"));
    connect(_stop_action, &QAction::triggered, &_run_management, &RunManagement::stopModeling);
    run_menu->addAction(_stop_action);

    run_menu->addSeparator();


    _view_menu = menuBar()->addMenu(tr("&View"));
    connect(_view_menu, &QMenu::aboutToShow, this, &MainWindow::updateViewMenu);

    _quick_action = new QAction(tr("&Quick actions"), this);
    _quick_action->setCheckable(true);
    // _quick_action->setChecked(true); /// @todo Нужно принимать из сохранённого состояния
    _quick_action->setStatusTip(tr("Show or hide quick actions"));
    _quick_action->setShortcut(Qt::ALT + Qt::Key_1);

    _modeling_action = new QAction(tr("&Modeling actions"), this);
    _modeling_action->setCheckable(true);
    // _modeling_action->setChecked(true); /// @todo Нужно принимать из сохранённого состояния
    _modeling_action->setStatusTip(tr("Show or hide modeling actions"));
    _modeling_action->setShortcut(Qt::ALT + Qt::Key_2);

    _statusbar_action = new QAction(tr("&Status bar"), this);
    _statusbar_action->setCheckable(true);
    _statusbar_action->setChecked(true); /// @todo Нужно принимать из сохранённого состояния
    _statusbar_action->setStatusTip(tr("Show or hide status bar"));
    connect(_statusbar_action, &QAction::triggered, this, &MainWindow::statusBarViewing);

    _message_dock_action = _global_messages_dock->toggleViewAction();
    _message_dock_action->setStatusTip(tr("Show or hide global messages panel"));
    _message_dock_action->setText(tr("Global &Messages"));

    _window_menu = menuBar()->addMenu(tr("&Window"));
    connect(_window_menu, &QMenu::aboutToShow, this, &MainWindow::updateWindowMenu);

    // if (!_panels.empty()) {
    //     int i = 3;
    //     for(PanelPluginData & ppd : _panels)
    //         for(PanelData & pd : ppd.panels) 
    //             if (shell::check(ppd.factory->supports(),shell::PS_Pan_Singleton)) {
    //                 QString panel_title = combinePanelTitle(ppd.factory,pd.adaptor->title());
    //                 ppd.menu_action = new QAction(panel_title, this);
    //                 ppd.menu_action->setCheckable(true);
    //                 ppd.menu_action->setChecked(pd.dock->isVisible());
    //                 ppd.menu_action->setStatusTip(tr("Show or hide %1").arg(panel_title));
    //                 if (i < 10)
    //                     ppd.menu_action->setShortcut(Qt::ALT + Qt::Key_0 + i);

    //                 connect(ppd.menu_action, &QAction::triggered, pd.dock, &QDockWidget::setVisible);
    //                 connect(pd.dock, &QDockWidget::visibilityChanged, ppd.menu_action, &QAction::setChecked);
    //                 i++;
    //             }
    // }

    updateViewMenu();

    const QIcon document_close_icon = QIcon::fromTheme("document-close", QIcon(":/images/document-close"));
    _close_act = new QAction(document_close_icon, tr("Cl&ose"), this);
    _close_act->setStatusTip(tr("Close the active window"));
    connect(_close_act, &QAction::triggered,
            _mdi_area, &QMdiArea::closeActiveSubWindow);

    _close_all_act = new QAction(tr("Close &All"), this);
    _close_all_act->setStatusTip(tr("Close all the windows"));
    connect(_close_all_act, &QAction::triggered, _mdi_area, &QMdiArea::closeAllSubWindows);

    _tile_act = new QAction(tr("&Tile"), this);
    _tile_act->setStatusTip(tr("Tile the windows"));
    connect(_tile_act, &QAction::triggered, _mdi_area, &QMdiArea::tileSubWindows);

    _cascade_act = new QAction(tr("&Cascade"), this);
    _cascade_act->setStatusTip(tr("Cascade the windows"));
    connect(_cascade_act, &QAction::triggered, _mdi_area, &QMdiArea::cascadeSubWindows);

    const QIcon split_horizontally_icon = QIcon::fromTheme("view-split-left-right", QIcon(":/images/view-split-top-bottom"));
    _split_horizontally_action = new QAction(split_horizontally_icon, tr("Split &horizontally"), this);
    _split_horizontally_action->setCheckable(true);
    _split_horizontally_action->setChecked(false);
    _split_horizontally_action->setStatusTip(tr("Split/merge document view horizontally"));
    connect(_split_horizontally_action, &QAction::triggered, this, &MainWindow::regroupDocumentHorizontally);

    const QIcon split_vertically_icon = QIcon::fromTheme("view-split-top-bottom", QIcon(":/images/view-split-left-right"));
    _split_vertically_action = new QAction(split_vertically_icon, tr("Split &vertically"), this);
    _split_vertically_action->setCheckable(true);
    _split_vertically_action->setChecked(false);
    _split_vertically_action->setStatusTip(tr("Split/merge document view vertically"));
    connect(_split_vertically_action, &QAction::triggered, this, &MainWindow::regroupDocumentVertically);

    _previous_act = new QAction(tr("Pre&vious"), this);
    _previous_act->setShortcuts(QKeySequence::NextChild); // Последовательности специально перепутаны
    _previous_act->setStatusTip(tr("Move the focus to the previous window"));
    connect(_previous_act, &QAction::triggered, this, [this]{
                _mdi_area->stepDocumentHistory(ShellMdiArea::HistoryStepDirection::Previous, true);                
            });

    _next_act = new QAction(tr("Oldest"), this);
    _next_act->setShortcuts(QKeySequence::PreviousChild); // Последовательности специально перепутаны
    _next_act->setStatusTip(tr("Move the focus to the oldest window"));
    connect(_next_act, &QAction::triggered, this, [this]{
                _mdi_area->stepDocumentHistory(ShellMdiArea::HistoryStepDirection::Oldest, true);                
            });

    _window_menu_separator_act = new QAction(this);
    _window_menu_separator_act->setSeparator(true);

    updateWindowMenu();

    menuBar()->addSeparator();

    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));

    const QIcon helpIcon = QIcon::fromTheme("system-help", QIcon(":/images/system-help"));
    _help_action = helpMenu->addAction(helpIcon, tr("&Help"), this, &MainWindow::help);
    _help_action->setStatusTip(tr("Show the application's Help"));

    helpMenu->addSeparator();

    const QIcon aboutIcon = QIcon::fromTheme("help-about", QIcon(":/images/help-about"));
    QAction *aboutAct = helpMenu->addAction(aboutIcon, tr("&About"), this, &MainWindow::about);
    aboutAct->setStatusTip(tr("Show the application's About box"));

    QAction *aboutQtAct = helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
    aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
}

void MainWindow::createToolbar()
{
    _main_toolbar = addToolBar(tr("Quick actions"));
    connect(_quick_action, &QAction::triggered, _main_toolbar, &QToolBar::setVisible);
    connect(_main_toolbar, &QToolBar::visibilityChanged, _quick_action, &QAction::setChecked);

    _main_toolbar->setFloatable(true); /// @todo Нужно ли запрещать отделение панели инструментов от окна?
    _main_toolbar->setAllowedAreas(Qt::ToolBarArea::AllToolBarAreas);
    _main_toolbar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    _main_toolbar->setIconSize(QSize(32,32));
    _main_toolbar->setObjectName(_main_toolbar->windowTitle());

    // Уменьшаем размер шрифта относительно масштабирования экрана
    QFont font = _main_toolbar->font();
    font.setPointSize(qMax(6,font.pointSize()*6/10));
    _main_toolbar->setFont(font);

    // Самый простой способ добавления кнопки на панель быстрого доступа
    _main_toolbar->addAction(_set_work_dir_action);
    _main_toolbar->addSeparator();
    _main_toolbar->addAction(_new_action);
    _main_toolbar->addAction(_open_action);
    _main_toolbar->addAction(_save_action);
    _main_toolbar->addAction(_save_all_action);
    _main_toolbar->addSeparator();
    _main_toolbar->addAction(_print_action);
    _main_toolbar->addAction(_print_preview_action);
    _main_toolbar->addSeparator();
    _main_toolbar->addAction(_edit_undo_action);
    _main_toolbar->addAction(_edit_redo_action);
#ifndef QT_NO_CLIPBOARD
    _main_toolbar->addSeparator();
    _main_toolbar->addAction(_cut_action);
    _main_toolbar->addAction(_copy_action);
    _main_toolbar->addAction(_paste_action);
#endif

    // _toolbar->addSeparator();
    // _toolbar->addAction(_find_action);
    // _toolbar->addAction(_replace_action);

    /// @todo Добавление чекбоксов на тулбар требует некоторых действий, т.к.
    /// например, управление видимостью панелей происходит из разных мест, а просто
    /// добавление с QAction работает плохо. Кроме того, желательно построить группировку
    /// ряда кнопок. В общем, нужно реализовать структуры, упрощающие этот код.
    _main_toolbar->addSeparator();
    QCheckBox *status_bar_vision = new QCheckBox(_statusbar_action->toolTip(), this);
    status_bar_vision->setFocusPolicy(Qt::NoFocus);
    status_bar_vision->setStatusTip(_statusbar_action->statusTip());
    status_bar_vision->setChecked(_statusbar_action->isChecked());
    connect(status_bar_vision, &QAbstractButton::toggled,
            _statusbar_action, &QAction::setChecked);
    connect(status_bar_vision, &QAbstractButton::toggled,
            statusBar(), &QStatusBar::setVisible);
    connect(_statusbar_action, &QAction::toggled,
            status_bar_vision, &QAbstractButton::setChecked);

    QCheckBox *message_panel_vision = new QCheckBox(_message_dock_action->toolTip(), this);
    message_panel_vision->setFocusPolicy(Qt::NoFocus);
    message_panel_vision->setStatusTip(_message_dock_action->statusTip());
    connect(message_panel_vision, &QAbstractButton::toggled,
            _message_dock_action, &QAction::setChecked);
    connect(message_panel_vision, &QAbstractButton::toggled,
            _global_messages_dock, &QDockWidget::setVisible);
    connect(_message_dock_action, &QAction::toggled,
            message_panel_vision, &QAbstractButton::setChecked);

    QWidget * checks_group = new QWidget();
    QVBoxLayout *layout = new QVBoxLayout(checks_group);
    layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    layout->addWidget(status_bar_vision);
    layout->addWidget(message_panel_vision);
    _main_toolbar->addWidget(checks_group);
    _main_toolbar->addSeparator();

    /// @attention Похоже где-то переборщил с коннектами - вероятно, из-за зацикливания неопределённое поведение при вложенных панелях.
    /// Проблема в том, что существует три точки управления панелями: через кнопку с крестиком на самой панели,
    /// через меню и через тулбар (который подключается ниже). Скорее всего, простое добавление соединений между всеми тремя узлами
    /// приводит к зацикливанию. Нужно разбираться.

    // QWidget * panels_group = new QWidget();
    // QVBoxLayout *panel_layout = new QVBoxLayout(panels_group);
    // panel_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);

    // for(PanelPluginData & pd : _panels) {
    //     QCheckBox * cb = new QCheckBox(pd.name);
    //     cb->setFocusPolicy(Qt::NoFocus);
    //     cb->setChecked(true); /// @todo Нужно принимать из интерфейса панели и/или из сохранённого состояния панели
    //     cb->setStatusTip(pd.menu_action->statusTip());
    //
    //     connect(cb, &QCheckBox::toggled,
    //             pd.menu_action, &QAction::setChecked);
    //     connect(cb, &QCheckBox::toggled,
    //             pd.dock, &QDockWidget::setVisible);
    //     connect(pd.menu_action, &QAction::toggled,
    //             cb, &QCheckBox::setChecked);
    //     connect(pd.dock, &QDockWidget::visibilityChanged,
    //             cb, &QCheckBox::setChecked);
    //     panel_layout->addWidget(cb);
    // }
    // _toolbar->addWidget(panels_group);
    // _toolbar->addSeparator();

    _main_toolbar->addAction(_help_action);

    _run_toolbar = addToolBar(tr("Run actions"));
    connect(_modeling_action, &QAction::triggered, _run_toolbar, &QToolBar::setVisible);
    connect(_run_toolbar, &QToolBar::visibilityChanged, _modeling_action, &QAction::setChecked);

    _run_toolbar->setFloatable(true); /// @todo Нужно ли запрещать отделение панели инструментов от окна?
    _run_toolbar->setAllowedAreas(Qt::ToolBarArea::AllToolBarAreas);
    _run_toolbar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
    _run_toolbar->setIconSize(QSize(32,32));
    _run_toolbar->setFont(font);
    _run_toolbar->setObjectName(_run_toolbar->windowTitle());

    _run_toolbar->addAction(_run_action);
    _run_toolbar->addAction(_pause_action);
    _run_toolbar->addAction(_stop_action);

    _run_toolbar->addSeparator();

    _runners_combo = new QComboBox();
    _runners_combo->setEnabled(false);
    _runners_combo->setFocusPolicy(Qt::FocusPolicy::NoFocus);
    _runners_combo->setToolTip(tr("Runner server"));
    _runners_combo->setStatusTip(tr("Choose a runner"));
    _runners_combo->setMinimumWidth(120);

    _run_auto_check = new QCheckBox("Run automatic");
    _run_auto_check->setEnabled(false);
    _run_auto_check->setFocusPolicy(Qt::FocusPolicy::NoFocus);
    _run_auto_check->setToolTip(tr("Run automatic on open or change content of document"));
    _run_auto_check->setStatusTip(tr("Run automatic on open or change content of document"));
    connect(_run_auto_check, &QAbstractButton::toggled, &_run_management, &RunManagement::checkAutoRun);

    QWidget * runners_group = new QWidget();
    QVBoxLayout * runners_layout = new QVBoxLayout(runners_group);
    runners_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    runners_layout->addWidget(_run_auto_check);
    runners_layout->addWidget(_runners_combo);
    _run_toolbar->addWidget(runners_group);
    _run_toolbar->addSeparator();

    connect(_runners_combo, QOverload<int>::of(&QComboBox::currentIndexChanged), &_run_management, &RunManagement::updateRunnerWidget);

    QWidget * run_options_group = new QWidget();
    _run_options_layout = new QVBoxLayout(run_options_group);
    _run_options_layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    _run_options_layout->addWidget(new QLabel(tr("Options:")));
    _run_toolbar->addWidget(run_options_group);
}

void MainWindow::createStatusBar()
{
    _modeling_state = new QLabel;
    _modeling_time = new QLabel;
    _document_info = new QLabel;
    _cursor_position_info = new QLabel;
    _statistics_info = new QLabel;
    statusBar()->addPermanentWidget(_modeling_state);
    statusBar()->addPermanentWidget(new QLabel(" "));
    statusBar()->addPermanentWidget(_modeling_time);
    statusBar()->addPermanentWidget(new QLabel(" "));
    statusBar()->addPermanentWidget(_document_info);
    statusBar()->addPermanentWidget(new QLabel(" "));
    statusBar()->addPermanentWidget(_cursor_position_info);
    statusBar()->addPermanentWidget(new QLabel(" "));
    statusBar()->addPermanentWidget(_statistics_info);
    statusBar()->showMessage(tr("Ready"));
}

void MainWindow::readSettings()
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
    const QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
    if (geometry.isEmpty()) {
        const QRect availableGeometry = screen()->availableGeometry();
        resize(availableGeometry.width() / 3, availableGeometry.height() / 2);
        move((availableGeometry.width() - width()) / 2,
             (availableGeometry.height() - height()) / 2);
    } else {
        restoreGeometry(geometry);
    }

    restoreState(settings.value("windowState").toByteArray());

    const QStringList recentDirs = readRecentDirs(settings);

    if (!recentDirs.isEmpty()) 
        setHomeDirectory(recentDirs.first());
}

void MainWindow::writeSettings()
{
    QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());

    settings.setValue("geometry", saveGeometry());
    settings.setValue("windowState", saveState());
}

DocumentWindow *MainWindow::activeDocumentWindow() const
{
    QMdiSubWindow * subwin = _mdi_area->activeSubWindow();

    if (subwin) {
        DocumentWindow * document_window = qobject_cast<DocumentWindow *>(subwin->widget());
        Q_ASSERT(document_window);
        return document_window;
    }

    return nullptr;
}

QMdiSubWindow *MainWindow::findMdiChild(const QString &file_name) const
{
    QString canonicalFilePath = QFileInfo(file_name).canonicalFilePath();

    const QList<QMdiSubWindow *> subWindows = _mdi_area->subWindowList();
    for (QMdiSubWindow *subwin : subWindows) {
        DocumentWindow *document_window = qobject_cast<DocumentWindow *>(subwin->widget());
        if (document_window->currentFile() == canonicalFilePath)
            return subwin;
    }
    return nullptr;
}

template <typename INTERFACE>
void MainWindow::loadPlugins(QMap<QString,INTERFACE *> & container, QString mask)
{
    QDir dir(QCoreApplication::applicationDirPath()+"/plugins");

    sendGlobal(tr("Scanning directory '%1' for plugins by mask '%2'...")
               .arg(dir.path())
               .arg(mask));

    dir.setFilter(QDir::Files | QDir::NoSymLinks);
    dir.setNameFilters({mask});
    QFileInfoList list = dir.entryInfoList();

    sendGlobal(tr("Plugins found: %1").arg(list.size()));

    for (int i = 0; i < list.size(); ++i) {
        QFileInfo       fileInfo = list.at(i);
        QPluginLoader   pluginLoader(fileInfo.absoluteFilePath());
        QObject *       plugin = pluginLoader.instance();
        INTERFACE *     factory = qobject_cast<INTERFACE *>(plugin);

        if (factory == nullptr) {
            sendGlobal(tr("Some errors occurred during plugin '%1' load:").arg(fileInfo.baseName()),
                    shell::MessageSeverity::Error);
            sendGlobal(pluginLoader.errorString(),
                    shell::MessageSeverity::Error);
            continue;
        }

        auto version = factory->version();
        if (version.major() != shell::Version_Major || version.minor() > shell::Version_Minor) {
            sendGlobal(tr("Incorrect version of plugin '%1'").arg(fileInfo.baseName()),
                    shell::MessageSeverity::Error);
            continue;
        }
        container.insert(fileInfo.baseName(),factory);
        sendGlobal(tr("Plugin was successfully loaded: '%1'").arg(fileInfo.baseName()));
    }
}

QString MainWindow::combinePanelTitle(shell::Panel_plugin * plugin, const QString & title)
{
    Q_ASSERT(plugin);

    QString panel_title = plugin->designation();

    // if (shell::check(plugin->supports(), shell::PS_Pan_Singleton))

    if (panel_title.isEmpty())
        panel_title = title;
    else
        panel_title += title.isEmpty() ? "" : ": " + title;

    return panel_title;
}

